﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Perceiveit.Xml
{
    public class XKeyedList<TKey,TItem> : XKeyedCollection<TKey,TItem>
        where TItem : XEntity
    {
        #region ivars

        private XProperty<TKey> _keyprop;

        #endregion


        #region public override bool IsReadOnly {get;}

        /// <summary>
        /// Identifies if this KeyedList is read only
        /// </summary>
        public override bool IsReadOnly{ get { return false; } }

        #endregion


        //
        // .ctor
        //

        #region public XKeyedList(XProperty<TKey> keyProperty, XFactory<TItem> factory)

        public XKeyedList(XProperty<TKey> keyProperty, XFactory<TItem> factory)
            : base(factory)
        {
            if (null == keyProperty)
                throw new ArgumentNullException("keyProperty");
            _keyprop = keyProperty;

        }

        #endregion

        //
        // public methods
        //

        #region public void Add(TItem item)

        public void Add(TItem item)
        {
            if (null == item)
                throw new ArgumentNullException("item");

            this.EnsurePopulated();
            if (!item.IsInitialized)
            {
                if (item.HasExistingNode)
                {
                    XName name = this.Factory.GetNameForEntity(item);
                    XElement ele = new XElement(name);

                    foreach (XElement node in item.Element.Elements())
                    {
                        ele.Add(node);
                    }
                    //ele.Add(item.Element.Nodes());
                    
                    if (item.Element.HasAttributes)
                        ele.Add(item.Element.Attributes());
                    
                    item.Init(ele);
                }
            }
            TKey key = this.GetKeyForItem(item);
            bool addedToDict = false;
            bool addedToCache = false;

            //attempt to perform the add as an atomic transaction
            try
            {
                //do this first because it may thow an exception
                this.KeyOffsets.Add(key, this.Cache.Count);
                addedToDict = true;

                this.RegisterCollectionChanged();

                this.Cache.Add(item);
                addedToCache = true;

                this.Element.Add(item.Element);
            }
            catch
            {
                try
                {
                    if (addedToDict)
                        this.KeyOffsets.Remove(key);
                    if (addedToCache)
                        this.Cache.Remove(item);
                }
                catch { }

                //now re-throw the exception
                throw;
            }

            
        }

        #endregion

        public void Remove(TItem item)
        {
            if (null == item)
                throw new ArgumentNullException("item");

            this.EnsurePopulated();
            TKey key = this.GetKeyForItem(item);
            this.Remove(key);
        }

        public void Remove(TKey key)
        {
            int index;
            this.EnsurePopulated();
            if (this.KeyOffsets.TryGetValue(key, out index))
            {
                TItem item = this.Cache[index];

                //attempt to perform an atomic change
                bool removedFromCache = false;
                bool removedFromDict = false;

                try
                {
                    this.Cache.RemoveAt(index);
                    removedFromCache = true;
                    
                    this.RegisterCollectionChanged();

                    removedFromDict = this.KeyOffsets.Remove(key);

                    item.Element.Remove();
                }
                catch
                {
                    try
                    {
                        if (removedFromDict)
                            this.KeyOffsets.Add(key, index);
                        if (removedFromCache)
                            this.Cache.Insert(index, item);
                    }
                    catch { }

                    //now rethrow the exception
                    throw;
                }
            }
        }

        public void Clear()
        {
            this.EnsurePopulated();
            foreach (TItem item in this.Cache)
            {
                item.Element.Remove();
            }
            this.Cache.Clear();
            this.KeyOffsets.Clear();
            this.RegisterCollectionChanged();
        }

        protected override TKey GetKeyForItem(TItem item)
        {
            return this._keyprop.GetValue(item);
        }
    }
}
